home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2009 February / PCWFEB09.iso / Software / Linux / Kubuntu 8.10 / kubuntu-8.10-desktop-i386.iso / casper / filesystem.squashfs / usr / lib / python2.5 / idlelib / IOBinding.py < prev    next >
Text File  |  2008-10-05  |  21KB  |  585 lines

  1. # changes by dscherer@cmu.edu
  2. #   - IOBinding.open() replaces the current window with the opened file,
  3. #     if the current window is both unmodified and unnamed
  4. #   - IOBinding.loadfile() interprets Windows, UNIX, and Macintosh
  5. #     end-of-line conventions, instead of relying on the standard library,
  6. #     which will only understand the local convention.
  7.  
  8. import os
  9. import types
  10. import sys
  11. import codecs
  12. import tempfile
  13. import tkFileDialog
  14. import tkMessageBox
  15. import re
  16. from Tkinter import *
  17. from SimpleDialog import SimpleDialog
  18.  
  19. from configHandler import idleConf
  20.  
  21. try:
  22.     from codecs import BOM_UTF8
  23. except ImportError:
  24.     # only available since Python 2.3
  25.     BOM_UTF8 = '\xef\xbb\xbf'
  26.  
  27. # Try setting the locale, so that we can find out
  28. # what encoding to use
  29. try:
  30.     import locale
  31.     locale.setlocale(locale.LC_CTYPE, "")
  32. except (ImportError, locale.Error):
  33.     pass
  34.  
  35. # Encoding for file names
  36. filesystemencoding = sys.getfilesystemencoding()
  37.  
  38. encoding = "ascii"
  39. if sys.platform == 'win32':
  40.     # On Windows, we could use "mbcs". However, to give the user
  41.     # a portable encoding name, we need to find the code page
  42.     try:
  43.         encoding = locale.getdefaultlocale()[1]
  44.         codecs.lookup(encoding)
  45.     except LookupError:
  46.         pass
  47. else:
  48.     try:
  49.         # Different things can fail here: the locale module may not be
  50.         # loaded, it may not offer nl_langinfo, or CODESET, or the
  51.         # resulting codeset may be unknown to Python. We ignore all
  52.         # these problems, falling back to ASCII
  53.         encoding = locale.nl_langinfo(locale.CODESET)
  54.         if encoding is None or encoding is '':
  55.             # situation occurs on Mac OS X
  56.             encoding = 'ascii'
  57.         codecs.lookup(encoding)
  58.     except (NameError, AttributeError, LookupError):
  59.         # Try getdefaultlocale well: it parses environment variables,
  60.         # which may give a clue. Unfortunately, getdefaultlocale has
  61.         # bugs that can cause ValueError.
  62.         try:
  63.             encoding = locale.getdefaultlocale()[1]
  64.             if encoding is None or encoding is '':
  65.                 # situation occurs on Mac OS X
  66.                 encoding = 'ascii'
  67.             codecs.lookup(encoding)
  68.         except (ValueError, LookupError):
  69.             pass
  70.  
  71. encoding = encoding.lower()
  72.  
  73. coding_re = re.compile("coding[:=]\s*([-\w_.]+)")
  74.  
  75. class EncodingMessage(SimpleDialog):
  76.     "Inform user that an encoding declaration is needed."
  77.     def __init__(self, master, enc):
  78.         self.should_edit = False
  79.  
  80.         self.root = top = Toplevel(master)
  81.         top.bind("<Return>", self.return_event)
  82.         top.bind("<Escape>", self.do_ok)
  83.         top.protocol("WM_DELETE_WINDOW", self.wm_delete_window)
  84.         top.wm_title("I/O Warning")
  85.         top.wm_iconname("I/O Warning")
  86.         self.top = top
  87.  
  88.         l1 = Label(top,
  89.             text="Non-ASCII found, yet no encoding declared. Add a line like")
  90.         l1.pack(side=TOP, anchor=W)
  91.         l2 = Entry(top, font="courier")
  92.         l2.insert(0, "# -*- coding: %s -*-" % enc)
  93.         # For some reason, the text is not selectable anymore if the
  94.         # widget is disabled.
  95.         # l2['state'] = DISABLED
  96.         l2.pack(side=TOP, anchor = W, fill=X)
  97.         l3 = Label(top, text="to your file\n"
  98.                    "Choose OK to save this file as %s\n"
  99.                    "Edit your general options to silence this warning" % enc)
  100.         l3.pack(side=TOP, anchor = W)
  101.  
  102.         buttons = Frame(top)
  103.         buttons.pack(side=TOP, fill=X)
  104.         # Both return and cancel mean the same thing: do nothing
  105.         self.default = self.cancel = 0
  106.         b1 = Button(buttons, text="Ok", default="active",
  107.                     command=self.do_ok)
  108.         b1.pack(side=LEFT, fill=BOTH, expand=1)
  109.         b2 = Button(buttons, text="Edit my file",
  110.                     command=self.do_edit)
  111.         b2.pack(side=LEFT, fill=BOTH, expand=1)
  112.  
  113.         self._set_transient(master)
  114.  
  115.     def do_ok(self):
  116.         self.done(0)
  117.  
  118.     def do_edit(self):
  119.         self.done(1)
  120.  
  121. def coding_spec(str):
  122.     """Return the encoding declaration according to PEP 263.
  123.  
  124.     Raise LookupError if the encoding is declared but unknown.
  125.     """
  126.     # Only consider the first two lines
  127.     str = str.split("\n")[:2]
  128.     str = "\n".join(str)
  129.  
  130.     match = coding_re.search(str)
  131.     if not match:
  132.         return None
  133.     name = match.group(1)
  134.     # Check whether the encoding is known
  135.     import codecs
  136.     try:
  137.         codecs.lookup(name)
  138.     except LookupError:
  139.         # The standard encoding error does not indicate the encoding
  140.         raise LookupError, "Unknown encoding "+name
  141.     return name
  142.  
  143.  
  144. class IOBinding:
  145.  
  146.     def __init__(self, editwin):
  147.         self.editwin = editwin
  148.         self.text = editwin.text
  149.         self.__id_open = self.text.bind("<<open-window-from-file>>", self.open)
  150.         self.__id_save = self.text.bind("<<save-window>>", self.save)
  151.         self.__id_saveas = self.text.bind("<<save-window-as-file>>",
  152.                                           self.save_as)
  153.         self.__id_savecopy = self.text.bind("<<save-copy-of-window-as-file>>",
  154.                                             self.save_a_copy)
  155.         self.fileencoding = None
  156.         self.__id_print = self.text.bind("<<print-window>>", self.print_window)
  157.  
  158.     def close(self):
  159.         # Undo command bindings
  160.         self.text.unbind("<<open-window-from-file>>", self.__id_open)
  161.         self.text.unbind("<<save-window>>", self.__id_save)
  162.         self.text.unbind("<<save-window-as-file>>",self.__id_saveas)
  163.         self.text.unbind("<<save-copy-of-window-as-file>>", self.__id_savecopy)
  164.         self.text.unbind("<<print-window>>", self.__id_print)
  165.         # Break cycles
  166.         self.editwin = None
  167.         self.text = None
  168.         self.filename_change_hook = None
  169.  
  170.     def get_saved(self):
  171.         return self.editwin.get_saved()
  172.  
  173.     def set_saved(self, flag):
  174.         self.editwin.set_saved(flag)
  175.  
  176.     def reset_undo(self):
  177.         self.editwin.reset_undo()
  178.  
  179.     filename_change_hook = None
  180.  
  181.     def set_filename_change_hook(self, hook):
  182.         self.filename_change_hook = hook
  183.  
  184.     filename = None
  185.     dirname = None
  186.  
  187.     def set_filename(self, filename):
  188.         if filename and os.path.isdir(filename):
  189.             self.filename = None
  190.             self.dirname = filename
  191.         else:
  192.             self.filename = filename
  193.             self.dirname = None
  194.             self.set_saved(1)
  195.             if self.filename_change_hook:
  196.                 self.filename_change_hook()
  197.  
  198.     def open(self, event=None, editFile=None):
  199.         if self.editwin.flist:
  200.             if not editFile:
  201.                 filename = self.askopenfile()
  202.             else:
  203.                 filename=editFile
  204.             if filename:
  205.                 # If the current window has no filename and hasn't been
  206.                 # modified, we replace its contents (no loss).  Otherwise
  207.                 # we open a new window.  But we won't replace the
  208.                 # shell window (which has an interp(reter) attribute), which
  209.                 # gets set to "not modified" at every new prompt.
  210.                 try:
  211.                     interp = self.editwin.interp
  212.                 except:
  213.                     interp = None
  214.                 if not self.filename and self.get_saved() and not interp:
  215.                     self.editwin.flist.open(filename, self.loadfile)
  216.                 else:
  217.                     self.editwin.flist.open(filename)
  218.             else:
  219.                 self.text.focus_set()
  220.             return "break"
  221.         #
  222.         # Code for use outside IDLE:
  223.         if self.get_saved():
  224.             reply = self.maybesave()
  225.             if reply == "cancel":
  226.                 self.text.focus_set()
  227.                 return "break"
  228.         if not editFile:
  229.             filename = self.askopenfile()
  230.         else:
  231.             filename=editFile
  232.         if filename:
  233.             self.loadfile(filename)
  234.         else:
  235.             self.text.focus_set()
  236.         return "break"
  237.  
  238.     eol = r"(\r\n)|\n|\r"  # \r\n (Windows), \n (UNIX), or \r (Mac)
  239.     eol_re = re.compile(eol)
  240.     eol_convention = os.linesep # Default
  241.  
  242.     def loadfile(self, filename):
  243.         try:
  244.             # open the file in binary mode so that we can handle
  245.             #   end-of-line convention ourselves.
  246.             f = open(filename,'rb')
  247.             chars = f.read()
  248.             f.close()
  249.         except IOError, msg:
  250.             tkMessageBox.showerror("I/O Error", str(msg), master=self.text)
  251.             return False
  252.  
  253.         chars = self.decode(chars)
  254.         # We now convert all end-of-lines to '\n's
  255.         firsteol = self.eol_re.search(chars)
  256.         if firsteol:
  257.             self.eol_convention = firsteol.group(0)
  258.             if isinstance(self.eol_convention, unicode):
  259.                 # Make sure it is an ASCII string
  260.                 self.eol_convention = self.eol_convention.encode("ascii")
  261.             chars = self.eol_re.sub(r"\n", chars)
  262.  
  263.         self.text.delete("1.0", "end")
  264.         self.set_filename(None)
  265.         self.text.insert("1.0", chars)
  266.         self.reset_undo()
  267.         self.set_filename(filename)
  268.         self.text.mark_set("insert", "1.0")
  269.         self.text.see("insert")
  270.         self.updaterecentfileslist(filename)
  271.         return True
  272.  
  273.     def decode(self, chars):
  274.         """Create a Unicode string
  275.  
  276.         If that fails, let Tcl try its best
  277.         """
  278.         # Check presence of a UTF-8 signature first
  279.         if chars.startswith(BOM_UTF8):
  280.             try:
  281.                 chars = chars[3:].decode("utf-8")
  282.             except UnicodeError:
  283.                 # has UTF-8 signature, but fails to decode...
  284.                 return chars
  285.             else:
  286.                 # Indicates that this file originally had a BOM
  287.                 self.fileencoding = BOM_UTF8
  288.                 return chars
  289.         # Next look for coding specification
  290.         try:
  291.             enc = coding_spec(chars)
  292.         except LookupError, name:
  293.             tkMessageBox.showerror(
  294.                 title="Error loading the file",
  295.                 message="The encoding '%s' is not known to this Python "\
  296.                 "installation. The file may not display correctly" % name,
  297.                 master = self.text)
  298.             enc = None
  299.         if enc:
  300.             try:
  301.                 return unicode(chars, enc)
  302.             except UnicodeError:
  303.                 pass
  304.         # If it is ASCII, we need not to record anything
  305.         try:
  306.             return unicode(chars, 'ascii')
  307.         except UnicodeError:
  308.             pass
  309.         # Finally, try the locale's encoding. This is deprecated;
  310.         # the user should declare a non-ASCII encoding
  311.         try:
  312.             chars = unicode(chars, encoding)
  313.             self.fileencoding = encoding
  314.         except UnicodeError:
  315.             pass
  316.         return chars
  317.  
  318.     def maybesave(self):
  319.         if self.get_saved():
  320.             return "yes"
  321.         message = "Do you want to save %s before closing?" % (
  322.             self.filename or "this untitled document")
  323.         m = tkMessageBox.Message(
  324.             title="Save On Close",
  325.             message=message,
  326.             icon=tkMessageBox.QUESTION,
  327.             type=tkMessageBox.YESNOCANCEL,
  328.             master=self.text)
  329.         reply = m.show()
  330.         if reply == "yes":
  331.             self.save(None)
  332.             if not self.get_saved():
  333.                 reply = "cancel"
  334.         self.text.focus_set()
  335.         return reply
  336.  
  337.     def save(self, event):
  338.         if not self.filename:
  339.             self.save_as(event)
  340.         else:
  341.             if self.writefile(self.filename):
  342.                 self.set_saved(1)
  343.                 try:
  344.                     self.editwin.store_file_breaks()
  345.                 except AttributeError:  # may be a PyShell
  346.                     pass
  347.         self.text.focus_set()
  348.         return "break"
  349.  
  350.     def save_as(self, event):
  351.         filename = self.asksavefile()
  352.         if filename:
  353.             if self.writefile(filename):
  354.                 self.set_filename(filename)
  355.                 self.set_saved(1)
  356.                 try:
  357.                     self.editwin.store_file_breaks()
  358.                 except AttributeError:
  359.                     pass
  360.         self.text.focus_set()
  361.         self.updaterecentfileslist(filename)
  362.         return "break"
  363.  
  364.     def save_a_copy(self, event):
  365.         filename = self.asksavefile()
  366.         if filename:
  367.             self.writefile(filename)
  368.         self.text.focus_set()
  369.         self.updaterecentfileslist(filename)
  370.         return "break"
  371.  
  372.     def writefile(self, filename):
  373.         self.fixlastline()
  374.         chars = self.encode(self.text.get("1.0", "end-1c"))
  375.         if self.eol_convention != "\n":
  376.             chars = chars.replace("\n", self.eol_convention)
  377.         try:
  378.             f = open(filename, "wb")
  379.             f.write(chars)
  380.             f.flush()
  381.             f.close()
  382.             return True
  383.         except IOError, msg:
  384.             tkMessageBox.showerror("I/O Error", str(msg),
  385.                                    master=self.text)
  386.             return False
  387.  
  388.     def encode(self, chars):
  389.         if isinstance(chars, types.StringType):
  390.             # This is either plain ASCII, or Tk was returning mixed-encoding
  391.             # text to us. Don't try to guess further.
  392.             return chars
  393.         # See whether there is anything non-ASCII in it.
  394.         # If not, no need to figure out the encoding.
  395.         try:
  396.             return chars.encode('ascii')
  397.         except UnicodeError:
  398.             pass
  399.         # If there is an encoding declared, try this first.
  400.         try:
  401.             enc = coding_spec(chars)
  402.             failed = None
  403.         except LookupError, msg:
  404.             failed = msg
  405.             enc = None
  406.         if enc:
  407.             try:
  408.                 return chars.encode(enc)
  409.             except UnicodeError:
  410.                 failed = "Invalid encoding '%s'" % enc
  411.         if failed:
  412.             tkMessageBox.showerror(
  413.                 "I/O Error",
  414.                 "%s. Saving as UTF-8" % failed,
  415.                 master = self.text)
  416.         # If there was a UTF-8 signature, use that. This should not fail
  417.         if self.fileencoding == BOM_UTF8 or failed:
  418.             return BOM_UTF8 + chars.encode("utf-8")
  419.         # Try the original file encoding next, if any
  420.         if self.fileencoding:
  421.             try:
  422.                 return chars.encode(self.fileencoding)
  423.             except UnicodeError:
  424.                 tkMessageBox.showerror(
  425.                     "I/O Error",
  426.                     "Cannot save this as '%s' anymore. Saving as UTF-8" \
  427.                     % self.fileencoding,
  428.                     master = self.text)
  429.                 return BOM_UTF8 + chars.encode("utf-8")
  430.         # Nothing was declared, and we had not determined an encoding
  431.         # on loading. Recommend an encoding line.
  432.         config_encoding = idleConf.GetOption("main","EditorWindow",
  433.                                              "encoding")
  434.         if config_encoding == 'utf-8':
  435.             # User has requested that we save files as UTF-8
  436.             return BOM_UTF8 + chars.encode("utf-8")
  437.         ask_user = True
  438.         try:
  439.             chars = chars.encode(encoding)
  440.             enc = encoding
  441.             if config_encoding == 'locale':
  442.                 ask_user = False
  443.         except UnicodeError:
  444.             chars = BOM_UTF8 + chars.encode("utf-8")
  445.             enc = "utf-8"
  446.         if not ask_user:
  447.             return chars
  448.         dialog = EncodingMessage(self.editwin.top, enc)
  449.         dialog.go()
  450.         if dialog.num == 1:
  451.             # User asked us to edit the file
  452.             encline = "# -*- coding: %s -*-\n" % enc
  453.             firstline = self.text.get("1.0", "2.0")
  454.             if firstline.startswith("#!"):
  455.                 # Insert encoding after #! line
  456.                 self.text.insert("2.0", encline)
  457.             else:
  458.                 self.text.insert("1.0", encline)
  459.             return self.encode(self.text.get("1.0", "end-1c"))
  460.         return chars
  461.  
  462.     def fixlastline(self):
  463.         c = self.text.get("end-2c")
  464.         if c != '\n':
  465.             self.text.insert("end-1c", "\n")
  466.  
  467.     def print_window(self, event):
  468.         tempfilename = None
  469.         saved = self.get_saved()
  470.         if saved:
  471.             filename = self.filename
  472.         # shell undo is reset after every prompt, looks saved, probably isn't
  473.         if not saved or filename is None:
  474.             # XXX KBK 08Jun03 Wouldn't it be better to ask the user to save?
  475.             (tfd, tempfilename) = tempfile.mkstemp(prefix='IDLE_tmp_')
  476.             filename = tempfilename
  477.             os.close(tfd)
  478.             if not self.writefile(tempfilename):
  479.                 os.unlink(tempfilename)
  480.                 return "break"
  481.         platform=os.name
  482.         printPlatform=1
  483.         if platform == 'posix': #posix platform
  484.             command = idleConf.GetOption('main','General',
  485.                                          'print-command-posix')
  486.             command = command + " 2>&1"
  487.         elif platform == 'nt': #win32 platform
  488.             command = idleConf.GetOption('main','General','print-command-win')
  489.         else: #no printing for this platform
  490.             printPlatform=0
  491.         if printPlatform:  #we can try to print for this platform
  492.             command = command % filename
  493.             pipe = os.popen(command, "r")
  494.             # things can get ugly on NT if there is no printer available.
  495.             output = pipe.read().strip()
  496.             status = pipe.close()
  497.             if status:
  498.                 output = "Printing failed (exit status 0x%x)\n" % \
  499.                          status + output
  500.             if output:
  501.                 output = "Printing command: %s\n" % repr(command) + output
  502.                 tkMessageBox.showerror("Print status", output, master=self.text)
  503.         else:  #no printing for this platform
  504.             message="Printing is not enabled for this platform: %s" % platform
  505.             tkMessageBox.showinfo("Print status", message, master=self.text)
  506.         if tempfilename:
  507.             os.unlink(tempfilename)
  508.         return "break"
  509.  
  510.     opendialog = None
  511.     savedialog = None
  512.  
  513.     filetypes = [
  514.         ("Python and text files", "*.py *.pyw *.txt", "TEXT"),
  515.         ("All text files", "*", "TEXT"),
  516.         ("All files", "*"),
  517.         ]
  518.  
  519.     def askopenfile(self):
  520.         dir, base = self.defaultfilename("open")
  521.         if not self.opendialog:
  522.             self.opendialog = tkFileDialog.Open(master=self.text,
  523.                                                 filetypes=self.filetypes)
  524.         filename = self.opendialog.show(initialdir=dir, initialfile=base)
  525.         if isinstance(filename, unicode):
  526.             filename = filename.encode(filesystemencoding)
  527.         return filename
  528.  
  529.     def defaultfilename(self, mode="open"):
  530.         if self.filename:
  531.             return os.path.split(self.filename)
  532.         elif self.dirname:
  533.             return self.dirname, ""
  534.         else:
  535.             try:
  536.                 pwd = os.getcwd()
  537.             except os.error:
  538.                 pwd = ""
  539.             return pwd, ""
  540.  
  541.     def asksavefile(self):
  542.         dir, base = self.defaultfilename("save")
  543.         if not self.savedialog:
  544.             self.savedialog = tkFileDialog.SaveAs(master=self.text,
  545.                                                   filetypes=self.filetypes)
  546.         filename = self.savedialog.show(initialdir=dir, initialfile=base)
  547.         if isinstance(filename, unicode):
  548.             filename = filename.encode(filesystemencoding)
  549.         return filename
  550.  
  551.     def updaterecentfileslist(self,filename):
  552.         "Update recent file list on all editor windows"
  553.         self.editwin.update_recent_files_list(filename)
  554.  
  555. def test():
  556.     root = Tk()
  557.     class MyEditWin:
  558.         def __init__(self, text):
  559.             self.text = text
  560.             self.flist = None
  561.             self.text.bind("<Control-o>", self.open)
  562.             self.text.bind("<Control-s>", self.save)
  563.             self.text.bind("<Alt-s>", self.save_as)
  564.             self.text.bind("<Alt-z>", self.save_a_copy)
  565.         def get_saved(self): return 0
  566.         def set_saved(self, flag): pass
  567.         def reset_undo(self): pass
  568.         def open(self, event):
  569.             self.text.event_generate("<<open-window-from-file>>")
  570.         def save(self, event):
  571.             self.text.event_generate("<<save-window>>")
  572.         def save_as(self, event):
  573.             self.text.event_generate("<<save-window-as-file>>")
  574.         def save_a_copy(self, event):
  575.             self.text.event_generate("<<save-copy-of-window-as-file>>")
  576.     text = Text(root)
  577.     text.pack()
  578.     text.focus_set()
  579.     editwin = MyEditWin(text)
  580.     io = IOBinding(editwin)
  581.     root.mainloop()
  582.  
  583. if __name__ == "__main__":
  584.     test()
  585.